/*
 * This file is part of the Serial Flash Universal Driver Library.
 *
 * Copyright (c) 2016-2018, Armink, <armink.ztl@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * 'Software'), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Function: Portable interface for each platform.
 * Created on: 2016-04-23
 */
#define LOG_TAG "sfud_port.c"

#include <sfud.h>
#include <stdarg.h>

/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>

#include "main.h"
#include "spi.h"
#include "FreeRTOS.h"
#include "semphr.h"
#include "perf_counter.h"
#include "elog.h"

/* USER CODE END Includes */

/* SFUD的sfud_flash结构体内部定义了user_data成员，用来保存用户私有数据，
 * 可以可根据需要自行设计数据结构
 */
 
#define INITED 1
#define UNINITED 0


typedef struct
{
    uint8_t inited;
    SemaphoreHandle_t *dma_semaph;      // SPI DMA发送接收完成信号量
    SemaphoreHandle_t *lock_mutex;      // SFUD 用的互斥锁，供sufd_lock()函数使用，互斥访问SFUD。
    
    SPI_HandleTypeDef *hspi;            // HAL库提供的SPI句柄
    
    GPIO_TypeDef *cs_port;              // CS引脚所在的端口
    uint16_t cs_pin;                    // CS引脚号    
}sfud_user_data_t;


SemaphoreHandle_t SPI1_DMA_Semaph;
SemaphoreHandle_t SPI1_Lock_Mutex;

sfud_user_data_t spi1_user_data = 
{
    .inited = UNINITED,
    .dma_semaph = &SPI1_DMA_Semaph,
    .lock_mutex = &SPI1_Lock_Mutex,
    .hspi       = &hspi1,
    .cs_port    = FLASH_CS_GPIO_Port,
    .cs_pin     = FLASH_CS_Pin,
};


/*******************重写SPI HAL库的回调函数***********************/

/**
 * @brief 			HAL_SPI_TxCpltCallback.
 *
 * @note 			SPI DMA接收完成中断回调函数，.	
 *  
 * @param[in] 		hspi spi句柄,包含spi外设配置信息
 * 
 * @return 			none.
 *
 * */
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
    /* yield变量 用于指示是否需要在中断服务程序中进行上下文切换*/
    BaseType_t yield = NULL;
    
    if(hspi == &hspi1)
    {
        /* 释放 SPI1_DMA 信号量， 通知其他任务SPI传输完成*/
        xSemaphoreGiveFromISR(SPI1_DMA_Semaph, &yield);
        
        /* 根据yield值，决定是否要进行上下文切换*/
        portYIELD_FROM_ISR(yield);
    }
}

/**
 * @brief 			HAL_SPI_RxCpltCallback.
 *
 * @note 			SPI DMA接收完成中断回调函数，.	
 *  
 * @param[in] 		hspi spi句柄,包含spi外设配置信息
 * 
 * @return 			none.
 *
 * */
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
{
    /* yield变量 用于指示是否需要在中断服务程序中进行上下文切换*/
    BaseType_t yield = NULL;
    
    if(hspi == &hspi1)
    {
        /* 释放 SPI1_DMA 信号量， 通知其他任务SPI传输完成*/
        xSemaphoreGiveFromISR(SPI1_DMA_Semaph, &yield);
        
        /* 根据yield值，决定是否要进行上下文切换*/
        portYIELD_FROM_ISR(yield);
    }
}


/**
 * @brief 			HAL_SPI_TxRxCpltCallback.
 *
 * @note 			SPI DMA传输/接收完成中断回调函数，.	
 *  
 * @param[in] 		hspi spi句柄,包含spi外设配置信息
 * 
 * @return 			none.
 *
 * */
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
    /* yield变量 用于指示是否需要在中断服务程序中进行上下文切换*/
    BaseType_t yield = NULL;
    
    if(hspi == &hspi1)
    {
        /* 释放 SPI1_DMA 信号量， 通知其他任务SPI传输完成*/
        xSemaphoreGiveFromISR(SPI1_DMA_Semaph, &yield);
        
        /* 根据yield值，决定是否要进行上下文切换*/
        portYIELD_FROM_ISR(yield);
    }
}


static char log_buf[256];

void sfud_log_debug(const char *file, const long line, const char *format, ...);

/**
 * SPI write data then read data
 */
static sfud_err spi_write_read(const sfud_spi *spi, const uint8_t *write_buf, size_t write_size, uint8_t *read_buf,
        size_t read_size) {
    sfud_err result = SFUD_SUCCESS;
//    uint8_t send_data, read_data;

    /**
     * add your spi write and read code
     */
    sfud_user_data_t *user_data = (sfud_user_data_t *)spi->user_data;
    
    /* 拉低CS, 开始通讯*/
    HAL_GPIO_WritePin(user_data->cs_port, user_data->cs_pin, GPIO_PIN_RESET);
            
    if (write_size)
    {
        HAL_SPI_Transmit_DMA(user_data->hspi, write_buf, write_size);
        
        /* 等待DMA传输完成*/
        xSemaphoreTake( *(user_data->dma_semaph), 0xFFFFFFFF);
    }
    
    if (read_size)
    {
        HAL_SPI_Receive_DMA(user_data->hspi, read_buf, read_size);
        
        /* 等待DMA传输完成*/
        xSemaphoreTake( *(user_data->dma_semaph), 0xFFFFFFFF);
    }
            
    /* 拉高CS, 结束通讯*/
    HAL_GPIO_WritePin(user_data->cs_port, user_data->cs_pin, GPIO_PIN_SET);        
    
    return result;
}
        
/**
 * @brief 			retry_delay_100us.
 *
 * @note 			使用pref_counter库，产生100us的延时.	
 *                  也可以自行用定时器实现，或者用DWT外设实现，或者for,while循环阻塞。
 *                  又或者对写入时间没有特别要求的，直接 vTaskDelayUntil(1) 或 vTaskDelay(1) 等等。
 * 
 * @return 			none.
 *
 * */
static void retry_delay_100us(void)
{
    delay_us(100);
}


/* lock SPI bus */
static void spi_lock(const sfud_spi *spi)
{
    sfud_user_data_t *user_data = (sfud_user_data_t *)spi->user_data;
    xSemaphoreTake(*(user_data->lock_mutex), 0xFFFFFFFF);
}
/* unlock SPI bus */
static void spi_unlock(const sfud_spi *spi)
{
    sfud_user_data_t *user_data = (sfud_user_data_t *)spi->user_data;
    xSemaphoreGive(*(user_data->lock_mutex));
}


#ifdef SFUD_USING_QSPI
/**
 * read flash data by QSPI
 */
static sfud_err qspi_read(const struct __sfud_spi *spi, uint32_t addr, sfud_qspi_read_cmd_format *qspi_read_cmd_format,
        uint8_t *read_buf, size_t read_size) {
    sfud_err result = SFUD_SUCCESS;

    /**
     * add your qspi read flash data code
     */

    return result;
}
#endif /* SFUD_USING_QSPI */


/*
    SFUD这里的设计思路是将SPI外设和GPIO等外设的初始化放在这里，这里STM32CubeMx中已经做好了外设初始化，
    这里可以做一些用户自定义的功能和资源的初始化
*/
sfud_err sfud_spi_port_init(sfud_flash *flash) {
    sfud_err result = SFUD_SUCCESS;

    /**
     * add your port spi bus and device object initialize code like this:
     * 1. rcc initialize
     * 2. gpio initialize
     * 3. spi device initialize
     * 4. flash->spi and flash->retry item initialize
     *    flash->spi.wr = spi_write_read; //Required
     *    flash->spi.qspi_read = qspi_read; //Required when QSPI mode enable
     *    flash->spi.lock = spi_lock;
     *    flash->spi.unlock = spi_unlock;
     *    flash->spi.user_data = &spix;
     *    flash->retry.delay = null;
     *    flash->retry.times = 10000; //Required
     */
    
    //1.首先判断传入的资源是否有效
    if(flash == NULL || flash->spi.name == NULL)
    {
        return SFUD_ERR_NOT_FOUND;
    }
    
    /* 判断传入的SPI设备是否为SPI1， 不同flash占用不同的SPI总线*/
    if(strcmp(flash->spi.name, "SPI1") == 0)
    {
        /* 同一个SPI设备，不重复初始化*/
        if(spi1_user_data.inited == UNINITED)
        {
            SPI1_DMA_Semaph = xSemaphoreCreateBinary();
            if (SPI1_DMA_Semaph == NULL)
            {
                return SFUD_ERR_NOT_FOUND;
            }
            SPI1_Lock_Mutex = xSemaphoreCreateMutex();
            if (SPI1_Lock_Mutex == NULL)
            {
                vSemaphoreDelete(SPI1_DMA_Semaph);
                return SFUD_ERR_NOT_FOUND;
            }
            
            spi1_user_data.dma_semaph = &SPI1_DMA_Semaph;
            spi1_user_data.lock_mutex = &SPI1_Lock_Mutex;
            spi1_user_data.inited = INITED;
        }
        
        spi1_user_data.cs_port = FLASH_CS_GPIO_Port;
        spi1_user_data.cs_pin = FLASH_CS_Pin;
        
        flash->spi.wr = spi_write_read;
        flash->spi.lock = spi_lock;
        flash->spi.unlock = spi_unlock;
        flash->retry.times = 10000;
        flash->retry.delay = retry_delay_100us;
        flash->spi.user_data = &spi1_user_data;
    }
    

    return result;
}

/**
 * This function is print debug info.
 *
 * @param file the file which has call this function
 * @param line the line number which has call this function
 * @param format output format
 * @param ... args
 */
void sfud_log_debug(const char *file, const long line, const char *format, ...) {
    va_list args;

    /* args point to the first variable parameter */
    va_start(args, format);
    //printf("[SFUD](%s:%ld) ", file, line);
    elog_raw("[SFUD](%s:%ld) ", file, line);
    /* must use vprintf to print */
    vsnprintf(log_buf, sizeof(log_buf), format, args);
    elog_raw("%s\r\n", log_buf);
    va_end(args);
}

/**
 * This function is print routine info.
 *
 * @param format output format
 * @param ... args
 */
void sfud_log_info(const char *format, ...) {
    va_list args;

    /* args point to the first variable parameter */
    va_start(args, format);
    elog_raw("[SFUD]");
    /* must use vprintf to print */
    vsnprintf(log_buf, sizeof(log_buf), format, args);
    elog_raw("%s\r\n", log_buf);
    va_end(args);
}
